﻿using System;
using Fabrication.Exceptions;
using Fabrication.Tests.TestClasses;
using NUnit.Framework;

namespace Fabrication.Tests
{
    [TestFixture]
    public class construction
    {
        [Test]
        public void should_fail_if_asked_to_setup_rules_for_an_interface()
        {
            Assert.Throws<ItemBuilderException>(() => new FabricationScope().Setup<IInterface>());
        }

        [Test]
        public void should_fail_if_asked_to_setup_rules_for_an_abstract_class()
        {
            Assert.Throws<ItemBuilderException>(() => new FabricationScope().Setup<AbstractClass>());
        }

        [Test]
        public void should_fail_if_asked_to_generate_an_interface()
        {
            Assert.Throws<ItemBuilderException>(() => new FabricationScope().Generate<IInterface>());
        }

        [Test]
        public void should_fail_if_asked_to_generate_an_abstract_class()
        {
            Assert.Throws<ItemBuilderException>(() => new FabricationScope().Generate<AbstractClass>());
        }

        [Test]
        public void should_build_item_with_default_contructor()
        {
            var builtItem = new FabricationScope().Generate<DefaultConstructor>();
            Assert.That(builtItem, Is.InstanceOf<DefaultConstructor>().And.Not.Null);
        }

        [Test]
        public void should_build_item_with_private_default_contructor()
        {
            var builtItem = new FabricationScope().Generate<PrivateDefaultConstructor>();
            Assert.That(builtItem, Is.InstanceOf<PrivateDefaultConstructor>().And.Not.Null);
        }

        [Test]
        public void should_build_item_with_parameterised_contructor()
        {
            var builtItem = new FabricationScope().Generate<ConstructorWithParameters>();
            Assert.That(builtItem, Is.InstanceOf<ConstructorWithParameters>().And.Not.Null);
        }

        [Test]
        public void should_build_item_with_multiple_parameter_constructor()
        {
            var builtItem = new FabricationScope().Generate<ConstructorWithMultipleParameters>();
            Assert.That(builtItem, Is.InstanceOf<ConstructorWithMultipleParameters>().And.Not.Null);
        }

        [Test]
        public void should_use_contruction_method_if_specified()
        {
            var fabricationScope = new FabricationScope();
            fabricationScope.Setup<PrivateConstructorWithParameters>()
                .ConstructUsing(PrivateConstructorWithParameters.CreateInstance);

            Assert.DoesNotThrow(() => fabricationScope.Generate<PrivateConstructorWithParameters>());
        }

        [Test]
        public void should_handle_mixing_contruction_method_with_rules()
        {
            var builtItem = new FabricationScope().Setup<PrivateConstructorWithParameters>()
                .ConstructUsing(PrivateConstructorWithParameters.CreateInstance)
                .For(x => x.StringProperty).Use("MyValue")
                .Generate();

            Assert.That(builtItem.StringProperty, Is.EqualTo("MyValue"));
        }

        [Test]
        public void should_fail_for_properties_that_cannot_be_constructed()
        {
            var fabricationScope = new FabricationScope();
            fabricationScope.Setup<SinglePropertyClass<PrivateConstructorWithParameters>>();

            Assert.Throws<ItemBuilderException>(() => fabricationScope.Generate<SinglePropertyClass<PrivateConstructorWithParameters>>());
        }

        [Test]
        public void should_set_default_values_for_constructor_parameters()
        {
            var builtItem = new FabricationScope().Generate<ConstructorWithMultipleParameters>();

            Assert.That(builtItem.Value, Is.EqualTo(1));
            Assert.That(builtItem.Name, Is.EqualTo("Name1"));
            Assert.That(builtItem.Id, Is.Not.Null.And.Not.EqualTo(Guid.Empty));
        }

        [Test]
        public void should_use_least_greedy_constructor()
        {
            var fabricationScope = new FabricationScope();
            Assert.DoesNotThrow(() => fabricationScope.Generate<MultipleConstructorsWithoutDefault>());
        }

        [Test]
        public void should_fail_if_class_has_only_private_parameterised_contructors()
        {
            var fabricationScope = new FabricationScope();
            Assert.Throws<ItemBuilderException>(() => fabricationScope.Generate<PrivateConstructorWithParameters>());
        }
    }

    public abstract class AbstractClass
    {
        
    }
}